﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using MoroMoro.Samples.ToDoList.Base.Application;
using MoroMoro.Samples.ToDoList.Application.ToDos;
using MoroMoro.Samples.ToDoList.Application.Categories;
using MoroMoro.Samples.ToDoList.Domain.ToDos;
using MoroMoro.Samples.ToDoList.Domain.Categories;

namespace MoroMoro.Samples.ToDoList.Application
{
    /// <summary>
    /// ToDoList ファサードを表します。
    /// </summary>
    [Serializable]
    public sealed class ToDoListFacade : WorkFacade
    {
        #region Constructors

        /// <summary>
        /// <see cref="ToDoListFacade"/> クラスの新しいインスタンスを初期化します。
        /// </summary>
        /// <param name="mode">作業モード。</param>
        public ToDoListFacade(WorkMode? mode = null)
            : base(WorkspaceSelector.Get(mode))
        {
        }

        #endregion

        #region Properties

        /// <summary>
        /// 現在クライアントに提供されている <see cref="ToDo"/> の一覧を取得または設定 (private) します。
        /// </summary>
        public ReadOnlyCollection<ToDo> ToDoList
        {
            get;
            private set;
        }

        /// <summary>
        /// 現在クライアントに提供されている<see cref="CategoryFilter">カテゴリ</see>の一覧を取得または設定 (private) します。
        /// </summary>
        public ReadOnlyCollection<Category> CategoryList
        {
            get;
            private set;
        }

        /// <summary>
        /// <see cref="ToDo.State">状態</see>のフィルタを取得または設定 (private) します。
        /// null の場合は全ての状態が対象となります。
        /// </summary>
        public ToDoState? StateFilter
        {
            get;
            private set;
        }

        /// <summary>
        /// <see cref="ToDo.Category">カテゴリ</see>のフィルタを取得または設定 (private) します。
        /// null の場合はカテゴリに属していない ToDo が対象となります。
        /// </summary>
        public Category CategoryFilter
        {
            get;
            private set;
        }

        #endregion

        #region Events

        /// <summary>
        /// ファサードの状態が更新されると発生します。
        /// </summary>
        public event EventHandler Refreshed;

        /// <summary>
        /// <see cref="ToDo"/> の一覧読み込みが失敗すると発生します。
        /// </summary>
        public event ErrorEventHandler<ToDoListLoadErrors> ToDoListLoadError;

        /// <summary>
        /// <see cref="Category"/> の一覧読み込みが失敗すると発生します。
        /// </summary>
        public event ErrorEventHandler<CategoryListLoadErrors> CategoryListLoadError;

        #endregion

        #region Methods

        /// <inheritdoc/>
        protected override void OnInitialize()
        {
            StateFilter = ToDoState.Touched;
            CategoryFilter = null;

            LoadCategories(false);
            LoadToDoList();
        }

        /// <summary>
        /// <see cref="ToDo"/> の一覧を読み込みます。
        /// </summary>
        /// <param name="raiseRefreshedEvent"><see cref="Refreshed"/> イベントを発生させるかどうかを示す値。</param>
        private void LoadToDoList(bool raiseRefreshedEvent = true)
        {
            var script = new ToDoListLoadScript(Workspace);
            script.StateFilter = StateFilter;
            script.CategoryFilter = CategoryFilter;
            script.Success += (sender, e) =>
            {
                ToDoList = new ReadOnlyCollection<ToDo>(e.Outputs.List);
                if (raiseRefreshedEvent && (Refreshed != null))
                {
                    Refreshed(this, EventArgs.Empty);
                }
            };
            script.Error += ToDoListLoadError;
            script.Execute();
        }

        /// <summary>
        /// <see cref="Category">カテゴリ</see>の一覧を読み込みます。
        /// </summary>
        /// <param name="raiseRefreshedEvent"><see cref="Refreshed"/> イベントを発生させるかどうかを示す値。</param>
        private void LoadCategories(bool raiseRefreshedEvent = true)
        {
            var script = new CategoryListLoadScript(Workspace);
            script.Success += (sender, e) =>
            {
                CategoryList = new ReadOnlyCollection<Category>(e.Outputs.List);
                if (raiseRefreshedEvent && (Refreshed != null))
                {
                    Refreshed(this, EventArgs.Empty);
                }
            };
            script.Error += CategoryListLoadError;
            script.Execute();
        }


        /// <summary>
        /// <see cref="StateFilter"/> を変更します。
        /// </summary>
        /// <param name="stateFilter">新しい <see cref="StateFilter"/> の値。</param>
        public void ChangeToDoStateFilter(ToDoState? stateFilter)
        {
            if (StateFilter == stateFilter)
            {
                return;
            }

            StateFilter = stateFilter;
            LoadToDoList();
        }

        /// <summary>
        /// <see cref="CategoryFilter"/> を変更します。
        /// </summary>
        /// <param name="categoryFilter">新しい <see cref="CategoryFilter"/> の値。</param>
        public void ChangeCategoryFilter(Category categoryFilter)
        {
            if (CategoryFilter == categoryFilter)
            {
                return;
            }

            CategoryFilter = categoryFilter;
            LoadToDoList();
        }


        /// <summary>
        /// <see cref="ToDo"/> の作成を目的として、<see cref="ToDo.Builder">ToDo ビルダ</see>を生成します。
        /// </summary>
        /// <returns><see cref="ToDo.Builder">ToDo ビルダ</see>。</returns>
        public ToDo.Builder CreateToDoBuilder()
        {
            var repository = Workspace.GetRepository<ToDo>();
            var validator = new ToDoConstraintValidator(repository);
            return new ToDo.Builder(validator);
        }

        /// <summary>
        /// <see cref="ToDo"/> の編集を目的として、<see cref="ToDo.Builder">ToDo ビルダ</see>を生成します。
        /// </summary>
        /// <param name="toDo">編集対象の <see cref="ToDo"/>。</param>
        /// <returns><see cref="ToDo.Builder">ToDo ビルダ</see>。</returns>
        public ToDo.Builder CreateToDoBuilder(ToDo toDo)
        {
            var repository = Workspace.GetRepository<ToDo>();
            var validator = new ToDoConstraintValidator(repository);
            return new ToDo.Builder(toDo, validator);
        }

        /// <summary>
        /// <see cref="ToDo"/> の作成を実行します。
        /// </summary>
        /// <param name="builder"><see cref="ToDo.Builder">ToDo ビルダ</see>。</param>
        /// <param name="errorHandler">エラーハンドラ。</param>
        public void ExecuteToDoCreation(ToDo.Builder builder, Action<ToDoCreationErrors> errorHandler)
        {
            var script = new ToDoCreationScript(Workspace);
            script.Builder = builder;
            script.Success += (sender, e) =>
            {
                StateFilter = e.Outputs.Entity.State;
                CategoryFilter = e.Outputs.Entity.Category;
                LoadToDoList();
            };
            script.Error += (sender, e) =>
            {
                errorHandler(e.Errors);
                e.Errors.HandledBy(CommitmentErrorsHandler);
            };
            script.Execute();
        }

        /// <summary>
        /// <see cref="ToDo"/> の編集を実行します。
        /// </summary>
        /// <param name="builder"><see cref="ToDo.Builder">ToDo ビルダ</see>。</param>
        /// <param name="errorHandler">エラーハンドラ。</param>
        public void ExecuteToDoEdition(ToDo.Builder builder, Action<ToDoEditionErrors> errorHandler)
        {
            var script = new ToDoEditionScript(Workspace);
            script.Builder = builder;
            script.Success += (sender, e) =>
            {
                LoadToDoList();
            };
            script.Error += (sender, e) =>
            {
                errorHandler(e.Errors);
                e.Errors.HandledBy(CommitmentErrorsHandler);
            };
            script.Execute();
        }

        /// <summary>
        /// <see cref="ToDo"/> の削除を実行します。
        /// </summary>
        /// <param name="targets">削除対象の <see cref="ToDo"/> の一覧。</param>
        /// <param name="errorHandler">エラーハンドラ。</param>
        public void ExecuteToDoDeletion(IEnumerable<ToDo> targets, Action<ToDoDeletionErrors> errorHandler)
        {
            var script = new ToDoDeletionScript(Workspace);
            script.Targets = targets;
            script.Success += (sender, e) =>
            {
                LoadToDoList();
            };
            script.Error += (sender, e) =>
            {
                errorHandler(e.Errors);
                e.Errors.HandledBy(CommitmentErrorsHandler);
            };
            script.Execute();
        }


        /// <summary>
        /// <see cref="Category">カテゴリ</see>の作成を目的として、<see cref="Category.Builder">カテゴリビルダ</see>を生成します。
        /// </summary>
        /// <returns><see cref="Category.Builder">カテゴリビルダ</see>。</returns>
        public Category.Builder CreateCategoryBuilder()
        {
            var repository = Workspace.GetRepository<Category>();
            var validator = new CategoryConstraintValidator(repository);
            return new Category.Builder(validator);
        }

        /// <summary>
        /// <see cref="Category">カテゴリ</see>の編集を目的として、<see cref="Category.Builder">カテゴリビルダ</see>を生成します。
        /// </summary>
        /// <param name="category">編集対象の<see cref="Category">カテゴリ</see>。</param>
        /// <returns><see cref="Category.Builder">カテゴリビルダ</see>。</returns>
        public Category.Builder CreateCategoryBuilder(Category category)
        {
            var repository = Workspace.GetRepository<Category>();
            var validator = new CategoryConstraintValidator(repository);
            return new Category.Builder(category, validator);
        }


        /// <summary>
        /// <see cref="Category">カテゴリ</see>の作成を実行します。
        /// </summary>
        /// <param name="builder"><see cref="Category.Builder">カテゴリビルダ</see>。</param>
        /// <param name="errorHandler">エラーハンドラ。</param>
        public void ExecuteCategoryCreation(Category.Builder builder, Action<CategoryCreationErrors> errorHandler)
        {
            var script = new CategoryCreationScript(Workspace);
            script.Builder = builder;
            script.Success += (sender, e) =>
            {
                LoadCategories();
            };
            script.Error += (sender, e) =>
            {
                errorHandler(e.Errors);
                e.Errors.HandledBy(CommitmentErrorsHandler);
            };
            script.Execute();
        }

        /// <summary>
        /// <see cref="Category">カテゴリ</see>の編集を実行します。
        /// </summary>
        /// <param name="builder"><see cref="Category.Builder">カテゴリビルダ</see>。</param>
        /// <param name="errorHandler">エラーハンドラ。</param>
        public void ExecuteCategoryEdition(Category.Builder builder, Action<CategoryEditionErrors> errorHandler)
        {
            var script = new CategoryEditionScript(Workspace);
            script.Builder = builder;
            script.Success += (sender, e) =>
            {
                LoadCategories();
            };
            script.Error += (sender, e) =>
            {
                errorHandler(e.Errors);
                e.Errors.HandledBy(CommitmentErrorsHandler);
            };
            script.Execute();
        }

        /// <summary>
        /// <see cref="Category">カテゴリ</see>の削除を実行します。
        /// </summary>
        /// <param name="targets">削除対象の<see cref="Category">カテゴリ</see>の一覧。</param>
        /// <param name="errorHandler">エラーハンドラ。</param>
        public void ExecuteCategoryDeletion(IEnumerable<Category> targets, Action<CategoryDeletionErrors> errorHandler)
        {
            var script = new CategoryDeletionScript(Workspace);
            script.Targets = targets;
            script.Success += (sender, e) =>
            {
                if (targets.Contains(CategoryFilter))
                {
                    CategoryFilter = null;
                }
                LoadCategories();
            };
            script.Error += (sender, e) =>
            {
                errorHandler(e.Errors);
                e.Errors.HandledBy(CommitmentErrorsHandler);
            };
            script.Execute();
        }


        /// <summary>
        /// <see cref="ToDo"/> の状態を進めます。
        /// </summary>
        /// <param name="targets">対象の <see cref="ToDo"/> の一覧。</param>
        /// <param name="state">遷移先となる<see cref="ToDo.State">状態</see></param>
        /// <param name="errorHandler">エラーハンドラ。</param>
        public void ExecuteToDoForward(IEnumerable<ToDo> targets, ToDoState state, Action<ToDoForwardErrors> errorHandler)
        {
            var script = new ToDoForwardScript(Workspace);
            script.Targets = targets;
            script.State = state;
            script.Success += (sender, e) =>
            {
                LoadToDoList();
            };
            script.Error += (sender, e) =>
            {
                errorHandler(e.Errors);
                e.Errors.HandledBy(CommitmentErrorsHandler);
            };
            script.Execute();
        }

        /// <summary>
        /// <see cref="ToDo"/> の状態を戻します。
        /// </summary>
        /// <param name="targets">対象の <see cref="ToDo"/> の一覧。</param>
        /// <param name="state">遷移先となる<see cref="ToDo.State">状態</see></param>
        /// <param name="errorHandler">エラーハンドラ。</param>
        public void ExecuteToDoReversion(IEnumerable<ToDo> targets, ToDoState state, Action<ToDoReversionErrors> errorHandler)
        {
            var script = new ToDoReversionScript(Workspace);
            script.Targets = targets;
            script.State = state;
            script.Success += (sender, e) =>
            {
                LoadToDoList();
            };
            script.Error += (sender, e) =>
            {
                errorHandler(e.Errors);
                e.Errors.HandledBy(CommitmentErrorsHandler);
            };
            script.Execute();
        }

        #endregion
    }
}
